﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using System.IO;

namespace Cqrs.Infrastructure.Aggregate
{
    public abstract class AggregateRootWithState : AggregateRoot
    {
        private StateTransitions _stateTransitions;
        //private XElement _stateMachineXml;
        //private XElement _stateMachineDesigner;

        //public XElement GetStateMachineXml()
        //{
        //    return _stateMachineXml == null ? null : _stateMachineXml;
        //}

        //public string GetStateMachineDesigner()
        //{
        //    return _stateMachineDesigner == null ? "" : _stateMachineDesigner.ToString();
        //}
        private string _smfFileLocation;
        public string GetSmfFileLocation()
        {
            return _smfFileLocation;
        }

        private SmfFileParser _smfFileParser;
        public SmfFileParser GetSmfFileParser()
        {
            return _smfFileParser;
        }

        private void Initialise(string smfFile, Assembly callingAssembly)
        {
            if (!System.IO.File.Exists(smfFile))
                throw new ArgumentException("State Configuration file: " + smfFile + " does not exist.");

            _smfFileParser = new SmfFileParser(smfFile);
            _smfFileLocation = smfFile;
            XElement scxml = _smfFileParser.GetStateMachineXml();
            _stateTransitions = new StateTransitions(scxml.ToString(), callingAssembly);
            CurrentState = _stateTransitions.InitialState;
        }



        public AggregateRootWithState()
        {
            Initialise(GetDefaultSmfFile(), System.Reflection.Assembly.GetCallingAssembly());
        }

        public AggregateRootWithState(string smfFile)
        {

            // todo - what if no Smf File has been set - error or default?
            if (string.IsNullOrEmpty(smfFile))
            {
                smfFile = GetDefaultSmfFile();
            }
            //_stateMachine = new StateMachine(
            //            @"C:\Users\Sean\Desktop\Thesis\Sample Projects\harmonic-thesis-project\UserStateChart.xml",
            //            System.Reflection.Assembly.GetCallingAssembly());

            //StateMachineRecord machine = StateMachineRecord.GetStateMachine(this.GetType(), stateMachineXml, stateMachineDesigner);

            //if (machine == null) throw new Exception("State Machine not found in database");            

            Initialise(smfFile, System.Reflection.Assembly.GetCallingAssembly());
        }

        private string GetDefaultSmfFile()
        {
            string baseDirectory = Directory.GetParent(this.GetType().Assembly.Location).FullName;
            return Path.Combine(baseDirectory, this.GetType().FullName + ".smf");
        }

        public State CurrentState
        {
            get;
            private set;
        }

        public State GetNextStateBasedOn(Type @event)
        {
            string nextStateName;
            State currentState;
            State nextState;

            if (!_stateTransitions.States.TryGetValue(this.CurrentState.Name, out currentState))
                throw new ApplicationException(
                    string.Format(
                    "The {0} state does not exist in the State Machine", this.CurrentState.Name));

            if (!_stateTransitions.States[this.CurrentState.Name]
                    .Transitions.TryGetValue(@event.Name, out nextStateName) == true)
                throw new ApplicationException(
                    string.Format(
                    "The event {0} is not allowed when in a State of {1}", @event.Name, this.CurrentState.Name));

            if (!_stateTransitions.States.TryGetValue(nextStateName, out nextState))
                throw new ApplicationException(
                    string.Format(
                    "The {0} state does not exist in the State Machine", nextStateName));

            return nextState;
        }

        public override void LoadsFromHistory(IEnumerable<Event> history)
        {
            foreach (var @event in history)
            {
                if (@event.GetType().IsSubclassOf(typeof(EventWithState)))
                    ChangeState(((EventWithState)@event).NextState, forceChange: true);

                base.ApplyChange(@event, isNew: false);
                Version = @event.Version;
            }
        }

        protected void ApplyChange(EventWithState @event)
        {
            State state = GetNextStateBasedOn(@event.GetType());
            if (@event.GetType().IsSubclassOf(typeof(EventWithState)))
                ChangeState(state.Name, forceChange: false);
            @event.NextState = state.Name;
            ApplyChange(@event, isNew: true);
        }

        //protected override void ApplyChange(Event @event)
        //{
        //    if (@event.GetType().IsSubclassOf(typeof(EventWithState)))
        //        ChangeState((EventWithState)@event, forceChange: false);
        //    ApplyChange(@event, isNew: true);
        //}

        internal void ChangeState(string nextState, bool forceChange)
        {
            if (_stateTransitions.States.Count(p => p.Key == nextState) > 0)
                this.CurrentState = _stateTransitions.States[nextState];
            else
            {
                if (forceChange)
                {
                    // Even if the State doesn't exists in the 
                    // State Machine, we still need to assign it 
                    // to the Aggregate, as it has been stored
                    // in the the database.
                    this.CurrentState = new State(nextState);
                }
                else
                    throw new ApplicationException(
                        String.Format("The {0} state was not found in the State Machine.", nextState));

            }
        }

        public static AggregateRootWithState Load(Type assemblyType, string smfFile)
        {
            AggregateRootWithState aggregateRoot;
            try
            {
                Object[] parameters = new Object[] { smfFile };
                aggregateRoot =
                    (AggregateRootWithState)Activator.CreateInstance(assemblyType, parameters);
                return aggregateRoot;
            }
            catch (TargetInvocationException ex)
            {
                throw ex.InnerException;
            }
        }
    }
}
